//@author A0096566M
#include "Parser.h"
#include <boost/algorithm/string.hpp>
#include "logger.hpp"

const string Parser::COMMAND_ADD = "add";
const string Parser::COMMAND_DISPLAY = "display";
const string Parser::COMMAND_DELETE = "delete";
const string Parser::COMMAND_CLEAR = "clear";
const string Parser::COMMAND_SORT = "sort";
const string Parser::COMMAND_SEARCH = "search";
const string Parser::COMMAND_FILTER = "filter";
const string Parser::COMMAND_EXIT = "exit";
const string Parser::COMMAND_INVALID = "invalid";
const string Parser::COMMAND_UPDATE = "update";
const string Parser::COMMAND_DONE = "done";
const string Parser::COMMAND_UNDONE = "undone";
const string Parser::COMMAND_HELP = "help";
const string Parser::COMMAND_REDO = "redo";
const string Parser::COMMAND_UNDO = "undo";

//A list of basic commands, in the default form.
const string BASIC_COMMANDS_STRING[] = {
    Parser::COMMAND_ADD,
    Parser::COMMAND_DISPLAY,
    Parser::COMMAND_DELETE,
    Parser::COMMAND_CLEAR,
    Parser::COMMAND_SORT,
    Parser::COMMAND_SEARCH,
    Parser::COMMAND_FILTER,
    Parser::COMMAND_EXIT,
    Parser::COMMAND_UPDATE,
    Parser::COMMAND_DONE,
    Parser::COMMAND_UNDONE,
    Parser::COMMAND_UNDO,
    Parser::COMMAND_REDO,
    Parser::COMMAND_HELP,
};
//Put the above in vector form.
const vector<string> BASIC_COMMANDS(BASIC_COMMANDS_STRING,end(BASIC_COMMANDS_STRING));

//@author A0096566M
unsigned int Parser::editDistance(const string &s1, const string & s2) {
        const size_t len1 = s1.size(), len2 = s2.size();
        vector<unsigned int> col(len2+1), prevCol(len2+1);

        for (unsigned int i = 0; i < prevCol.size(); i++)
                prevCol[i] = i;
        for (unsigned int i = 0; i < len1; i++) {
                col[0] = i+1;
                for (unsigned int j = 0; j < len2; j++)
                        col[j+1] = std::min( std::min( 1 + col[j], 1 + prevCol[1 + j]),
                                                                prevCol[j] + (s1[i]==s2[j] ? 0 : 1) );
                col.swap(prevCol);
        }
        return prevCol[len2];
}

// cParser old stuff first.
//@author A0078695H
Cmd * Parser::createAdd(vector<string> params, string orig){
    if (params.size() < 1) {
        return this->createInvalid(params, orig, ErrorType::ERROR_TOO_FEW_ARG);
    }
    return new CmdAdd(cmdTypes::ADD, params, orig);
}
//@author A0078695H
vector<int> Parser::parseDeleteRange(string argStr) {
	
	boost::trim_all(argStr);
	vector<string> raw_indices = tokenize(argStr, " ");
	vector<int> indices;
	char arr[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'};
	for (int i=0; i<raw_indices.size(); i++) {
		// check whether indices contain only digits and -
		
		for (int j=0; j<raw_indices[i].length(); j++) {
			bool legit = false;
			for (int k=0; k<11; k++) {
				if (raw_indices[i][j] == arr[k]) {
					legit = true;
					break;
				}
			}
			if (!legit) {
				throw "invalid arg";
			}
		}
		if (raw_indices[i].find('-') != string::npos &&
			raw_indices[i].find_first_of('-') != raw_indices[i].find_last_of('-')) {
				throw "invalid arg";
		}
		if (raw_indices[i].find('-') == string::npos) {
			indices.push_back(std::stoi(raw_indices[i]));
		}
		else {
			int dash_idx = raw_indices[i].find('-');
			int start = std::stoi(raw_indices[i].substr(0, dash_idx));
			int end = std::stoi(raw_indices[i].substr(dash_idx+1));
			for (int j=start; j<=end; j++) {
				indices.push_back(j);
			}
		}
	}
	return indices;
}
//@author A0078695H
Cmd * Parser::createDel(vector<string> params, string orig){
    if (params.size() != 1) {
        return (this->createInvalid(params, orig, ErrorType::ERROR_TOO_FEW_ARG));
    }
	assert(params.size() == 1);
	try {
	vector<int> indices = parseDeleteRange(params[0]);
    // check idx
    
		vector<int> ids;
		vector<bool> markDelete = vector<bool>(displayRecords->getRecordSize(), false);
		
		for (int i=0; i<indices.size(); i++) {
			int cur_index = indices[i];
			if (cur_index >= 1 && cur_index <= displayRecords->getRecordSize()) {
				markDelete[cur_index-1] = true;
			}
			else {
				return this->createInvalid(params, orig, ErrorType::ERROR_IDX_INVALID);
			}
		}
        for (int i=0; i<markDelete.size(); i++) {
			if (markDelete[i]) {
				int id = displayRecords->getData(i).getId();
				ids.push_back(id);
			}
		}

		return new CmdDel(cmdTypes::DEL, params, orig, ids);
    }
	catch (...) {
		return this->createInvalid(params, orig, ErrorType::ERROR_ARG_INVALID);
	}
}
//@author A0078695H
Cmd * Parser::createHelp(vector<string> params, string orig, string cmdTypeString) {
    cmdTypes helpT = cmdTypes::HELP;
    if (cmdTypeString == Parser::COMMAND_ADD) {
        helpT = cmdTypes::ADD;
    }
    else if (cmdTypeString == Parser::COMMAND_DISPLAY) {
        helpT = cmdTypes::DISPLAY;
    }
    else if (cmdTypeString == Parser::COMMAND_DELETE) {
        helpT = cmdTypes::DEL;
    }
    else if (cmdTypeString == Parser::COMMAND_CLEAR) {
        helpT = cmdTypes::CLEAR;
    }
    else if (cmdTypeString == Parser::COMMAND_SORT) {
        helpT = cmdTypes::SORT;
    }
    else if (cmdTypeString == Parser::COMMAND_SEARCH) {
        helpT = cmdTypes::SEARCH;
    }
    else if (cmdTypeString == Parser::COMMAND_EXIT) {
        helpT = cmdTypes::EXIT;
    }
    else if (cmdTypeString == Parser::COMMAND_UPDATE) {
        helpT = cmdTypes::UPDATE;
    }
    else if (cmdTypeString == Parser::COMMAND_DONE) {
        helpT = cmdTypes::DONE;
    }
    else if (cmdTypeString == Parser::COMMAND_UNDONE) {
        helpT = cmdTypes::UNDONE;
    }

    return new CmdHelp(cmdTypes::HELP, params, orig, helpT);
}
//@author A0078695H
Cmd * Parser::createDisplay(vector<string> params, string orig){
    return new CmdDisplay(cmdTypes::DISPLAY, params, orig);
}
//@author A0078695H
Cmd * Parser::createClear(vector<string> params, string orig){
    return new CmdClear(cmdTypes::CLEAR, params, orig);
}
//@author A0078695H
Cmd * Parser::createExit(vector<string> params, string orig){
    return new CmdExit(cmdTypes::EXIT, params, orig);
}

//@author A0101728M
Cmd * Parser::createSort(vector<string> params, string orig){
    return new CmdSort(cmdTypes::SORT, params, orig);
}

Cmd * Parser::createFilter(vector<string> params, string orig){
    return new CmdFilter(cmdTypes::FILTER, params, orig);
}

Cmd * Parser::createSearch(vector<string> params, string orig){
    if (params.size() < 1) {
        return (this->createInvalid(params, orig, ErrorType::ERROR_TOO_FEW_ARG));
    }
    return new CmdSearch(cmdTypes::SEARCH, params, orig);
}
//@author A0078695H
Cmd * Parser::createInvalid(vector<string> params, string orig, ErrorType errType){
    return new CmdInvalid(cmdTypes::INVALID, params, orig, errType);
}
//@author A0078695H
Cmd * Parser::createUpdate(vector<string> params, string orig) {
    vector<string> udtParams;

    if (params.size() < 1) {
        return (this->createInvalid(params, orig, ErrorType::ERROR_TOO_FEW_ARG));
    }
    else {
        splitCmdStringWithFirstDelim(params[0], ' ', udtParams);
    }
    // check idx
    try {
        int index = stoi(udtParams[0]);
        if (index >= 1 && index <= displayRecords->getRecordSize()) {
            // have to do deciding type of udt here because
            // cmdUpdate don't have records

            int id = displayRecords->getData(index-1).getId();
            return new CmdUpdate(cmdTypes::UPDATE, udtParams, orig, id);
        }
        else {
            return this->createInvalid(params, orig, ErrorType::ERROR_IDX_INVALID);
        }
    }
    catch (std::invalid_argument) {
        return this->createInvalid(params, orig, ErrorType::ERROR_ARG_INVALID);
    }
	catch (...) {
		return this->createInvalid(params, orig, ErrorType::ERROR_ARG_INVALID);
	}
}

Cmd* Parser::createUndo(vector<string> params, string orig) {
    bool met = true;
    ErrorType err;
    if (met) {
        return new CmdUndo(cmdTypes::UNDO, params, orig);
    }
    /*else {
        return new CmdInvalid(cmdTypes::INVALID, params, ErrorType::);
    }*/

}

Cmd* Parser::createRedo(vector<string> params, string orig) {
    bool met = true;
    ErrorType err;
    if (met) {
        return new CmdRedo(cmdTypes::REDO, params, orig);
    }
    /*else {
        return new CmdInvalid(, ErrorType::);
    }*/
}
//@author A0078695H
Cmd * Parser::createDone(vector<string> params, string orig) {
	if (params.size() != 1) {
		return (this->createInvalid(params, orig, ErrorType::ERROR_TOO_FEW_ARG));
	}
	assert(params.size() == 1);
	try {
		vector<int> indices = parseDeleteRange(params[0]);
	// check idx
	
		vector<int> ids;
		vector<bool> markDelete = vector<bool>(displayRecords->getRecordSize(), false);

		for (int i=0; i<indices.size(); i++) {
			int cur_index = indices[i];
			if (cur_index >= 1 && cur_index <= displayRecords->getRecordSize()) {
				markDelete[cur_index-1] = true;
			}
			else {
				return this->createInvalid(params, orig, ErrorType::ERROR_IDX_INVALID);
			}
		}
		for (int i=0; i<markDelete.size(); i++) {
			if (markDelete[i]) {
				int id = displayRecords->getData(i).getId();
				ids.push_back(id);
			}
		}
		//check whether to mark as done or undone
		vector<string> cmdArr = vector<string>();
		splitCmdStringWithFirstDelim(orig, ' ', cmdArr);
		string cmdTypeString = cmdArr[0];
		if (cmdTypeString == COMMAND_DONE) {
			return new CmdDone(cmdTypes::DONE, params, orig, ids);
		}
		else {
			return new CmdDone(cmdTypes::UNDONE, params, orig, ids);
		}
	}
	catch (...) {
		return this->createInvalid(params, orig, ErrorType::ERROR_ARG_INVALID);
	}
}

//@author A0096566M
vector<Cmd*> Parser::getCommandList(){
    return commandList;
}
//@author A0078695H
auto_ptr<Cmd> Parser::parse(string commandStr){
    vector<string> cmdArr = vector<string>();
    splitCmdStringWithFirstDelim(commandStr, ' ', cmdArr);
    assert(cmdArr.size() == 2 || cmdArr.size() == 1);
    string cmdTypeString = cmdArr[0];
    vector<string> params;
    if (cmdArr.size() == 2 ) {
        params.push_back(cmdArr[1]);
        boost::trim(cmdArr[1]);
        if ( cmdArr[1] == "?") {
            return auto_ptr<Cmd>(this->createHelp(params, commandStr, cmdTypeString));
        }
    }

    //Route command and parameters to respective functions to handle
    if( cmdTypeString == COMMAND_ADD){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as ADD command";
        return auto_ptr<Cmd>(this->createAdd(params, commandStr));
    }
    else if (cmdTypeString == COMMAND_DISPLAY) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as DISPLAY command";
        return auto_ptr<Cmd>(this->createDisplay(params, commandStr));
    }
    else if (cmdTypeString == COMMAND_DELETE) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as DELETE command";
        return auto_ptr<Cmd>(this->createDel(params, commandStr));
    }
    else if(cmdTypeString == COMMAND_CLEAR){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as CLEAR command";
        return auto_ptr<Cmd>(this->createClear(params, commandStr));
    }
    else if(cmdTypeString == COMMAND_SORT){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as SORT command";
        vector <string> tokens;
        if (cmdArr.size() == 2){
            tokens = Parser::tokenize(cmdArr[1], " ");
			if (tokens.size()>2 && (tokens[0] == CmdSort::ASCENDING || tokens[0] == CmdSort::DESCENDING)){
				if (std::find(CmdSort::SORT_TYPES.begin(), CmdSort::SORT_TYPES.end(), tokens[1]) == CmdSort::SORT_TYPES.end()){
					log(LOG_DEBUG) << "Parser::parse - " << "Invalid command because it has invalid parameters.\n";
					return auto_ptr<Cmd>(this->createInvalid(tokens, commandStr, ErrorType::ERROR_ARG_INVALID));
				}
			}
			else if (tokens.size()>2 && (std::find(CmdSort::SORT_TYPES.begin(), CmdSort::SORT_TYPES.end(), tokens[0]) == CmdSort::SORT_TYPES.end())){
				log(LOG_DEBUG) << "Parser::parse - " << "Invalid command because it has invalid parameters.\n";
				return auto_ptr<Cmd>(this->createInvalid(tokens, commandStr, ErrorType::ERROR_ARG_INVALID));
			}
			else if (tokens.size() == 1 && (std::find(CmdSort::SORT_TYPES.begin(), CmdSort::SORT_TYPES.end(), tokens[0]) == CmdSort::SORT_TYPES.end())){
				log(LOG_DEBUG) << "Parser::parse - " << "Invalid command because it has invalid parameters.\n";
				return auto_ptr<Cmd>(this->createInvalid(tokens, commandStr, ErrorType::ERROR_ARG_INVALID));
			}
        }
        return auto_ptr<Cmd>(this->createSort(tokens, commandStr));
    }
    else if (cmdTypeString == COMMAND_SEARCH){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as SEARCH command";
		if (cmdArr.size() == 2){
            vector<string> tokens = Parser::tokenize(cmdArr[1], " ");
            return auto_ptr<Cmd>(this->createSearch(tokens, commandStr));
        }
        else{
			log(LOG_DEBUG) << "Parser::parse - " << "Invalid command because it has too few parameters.\n";
            return auto_ptr<Cmd>(this->createInvalid(params, commandStr, ErrorType::ERROR_TOO_FEW_ARG));
        }
    }
    else if (cmdTypeString == COMMAND_FILTER){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as FILTER command";
        if (cmdArr.size() == 2){
			log(LOG_DEBUG) << "Parser::parse - " << "Command has parameter(s).";
            vector<string> tokens = Parser::tokenize(cmdArr[1], " ");
			log(LOG_DEBUG) << "Parser::parse - " << "tokens.size: " << tokens.size();
			if (tokens.size() == 1){
				log(LOG_DEBUG) << "Parser::parse - " << "Parameter: " << tokens[0];
				if (tokens[0] == CmdFilter::IN_ || tokens[0] == CmdFilter::OUT_){
					log(LOG_DEBUG) << "Parser::parse - " << "Invalid command because it has too few parameters.\n";
					return auto_ptr<Cmd>(this->createInvalid(params, commandStr, ErrorType::ERROR_TOO_FEW_ARG));
				}
				else {
					return auto_ptr<Cmd>(this->createFilter(tokens, commandStr));
				}
			}
			else {
				return auto_ptr<Cmd>(this->createFilter(tokens, commandStr));
			}
        }
        else{
            return auto_ptr<Cmd>(this->createInvalid(params, commandStr, ErrorType::ERROR_TOO_FEW_ARG));
        }
    }
    else if(cmdTypeString == COMMAND_EXIT){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as EXIT command";
        return auto_ptr<Cmd>(this->createExit(params, commandStr));
    }
    else if (cmdTypeString == COMMAND_UPDATE) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as UPDATE command";
        return auto_ptr<Cmd>(this->createUpdate(params, commandStr));
    }
    else if (cmdTypeString == COMMAND_DONE){
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as DONE command";
        return auto_ptr<Cmd>(this->createDone(params, commandStr));
    }
	else if (cmdTypeString == COMMAND_UNDONE){
			log(LOG_DEBUG) << "Parser::parse - " << "Identified as UNDONE command";
			return auto_ptr<Cmd>(this->createDone(params, commandStr));
	}
    else if (cmdTypeString == COMMAND_HELP) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as HELP command";
        return auto_ptr<Cmd>(this->createHelp(params, commandStr, cmdTypeString));
    }
    else if (cmdTypeString == COMMAND_UNDO) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as UNDO command";
        return auto_ptr<Cmd>(this->createUndo(params, commandStr));
    }
    else if (cmdTypeString == COMMAND_REDO) {
		log(LOG_DEBUG) << "Parser::parse - " << "Identified as REDO command";
        return auto_ptr<Cmd>(this->createRedo(params, commandStr));
    }
    else {
		log(LOG_DEBUG) << "Parser::parse - " << "Command not recognized";
        return auto_ptr<Cmd>(this->createInvalid(params, commandStr, ErrorType::ERROR_UNRECOG_CMD));
    }
}
//@author A0078695H
void Parser::splitStringWithDelim(const std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    //return elems;
}
//@author A0078695H
void Parser::splitCmdStringWithFirstDelim(const std::string &s, char delim, std::vector<std::string> &elems) {
    string cur = s;
    boost::algorithm::trim(cur);
    int res = cur.find(delim);

    if (res == string::npos) {
        elems.push_back(cur);
    }
    else {
        elems.push_back(cur.substr(0, res));
        elems.push_back(cur.substr(res+1));
    }
}
//@author A0078695H
void Parser::setDisplayRecords(Records* r){
    this->displayRecords = r;
}


// Starting from here fParser methods

//@author A0096566M
//Helper Function to tokenize strings
vector<string> Parser::tokenize(string s, string delim){
    vector<string> list;
    char *next_token = NULL;
    char* str = new char[1000];
    strcpy_s(str,  (size_t) 1000, s.c_str());
    char * pch = NULL;
    //pch = strtok(str,delim.c_str());
      pch = strtok_s( str, delim.c_str(), &next_token);
    while (pch != NULL)
    {
        list.push_back(pch);
        pch = strtok_s( NULL, delim.c_str(), &next_token);
        //pch = strtok(NULL, delim.c_str());
    }
    return list;
}
//@author A0096566M
//Concatenate(join strings) with " " in between each string.
string Parser::concatenate(vector<string> str){
    string joined;
    for(int i=0 ; i<str.size() ; i++ ){
        if(str[i] != ""){
            joined += str[i];
            if(i<str.size()-1){
                joined += " ";
            }
        }
    }
    return joined;
}

//@author A0096566M
//Check if string is basic command and return its index in BASIC_COMMANDS
//Return -1 if not a basic command.
int Parser::isBasicCommand(string s){
    for(int i=0 ; i<BASIC_COMMANDS.size() ; i++){
        if(s == BASIC_COMMANDS[i]){
            return i;
        }
    }
    return -1;
}

//@author A0096566M
//Gets strings in s which begins with toMatch
vector<string> Parser::filterBeginsWith(vector<string> s, string toMatch){
    vector<string> match;
    for(int i=0 ; i<s.size() ; i++){
        if(s[i].find(toMatch) == 0 ){
            match.push_back(s[i]);
        }
    }
    return match;
}

//@author A0096566M
//Find Dates in tokens
//Adds dates found to datesFound
//Adds original string to notDates, and replaces the tokens found that are dates to ""
//Returns number of dates found.
int Parser::findDates(vector<string> s, vector<Date>* datesFound, vector<string>* notDates){
    vector<string> tempNotDates;
    //Maximum tokens(space separated) in a date string, to reduce search time.
    int maxTokens = (int)s.size();//Change to 2 currently?
	int limitDatesFound = 2;
    //Find longest valid combination of token that are dates.
    for(int len=maxTokens ; len>0 ; len--){//length of string to check
        tempNotDates = s;
        for(int st=(int)(s.size()-len) ; st>=0 ; st--){//Start point of string to check
            //Concatenate strings[st] to strings[st+len], with " " in between.
            string cat; //= accumulate(s.begin()+st,s.begin()+st+len,string());
            tempNotDates = s;
            if(s[st] == "")//Prevent trying to match already matched field.
                continue;
			log(LOG_DEBUG) << "in findDates for loop";
			log(LOG_DEBUG) << "s.size(): " << s.size();
            for(int i=st ; i<st+len ; i++){	
				log(LOG_DEBUG) << "i: " << i;
                cat += s[i];
                if(i<st+len-1){
                    cat += " ";
                }
                tempNotDates[i] = "";
            }
            //Test for valid date.
            Date testDate;
            testDate.fromString(cat);
            if(testDate.isValid()){
				if(datesFound->size() < limitDatesFound){
					datesFound->push_back(testDate);
					//to remove extra ""s such that one date only leaves one "" in notDates in its place.
					for(int itr=0 ; itr<len-1 ; itr++){
						auto it = std::find(tempNotDates.begin(),tempNotDates.end(),"");
						if (it != tempNotDates.end()) {
						  tempNotDates.erase(it);
						}
					}
					s = tempNotDates;
					st -= len-1;
				}
            } else {
                //tempNotDates.push_back(cat);
            }
        }
        if(datesFound->size() > 0){
            //notDates->insert(notDates->begin(),tempNotDates.begin(),tempNotDates.end());
            //s = tempNotDates;
            //return datesFound->size();

        }
    }
    tempNotDates = s;
    notDates->insert(notDates->begin(),tempNotDates.begin(),tempNotDates.end());
    return datesFound->size();
}

//@author A0096566M
//Find tags in tokens
//Adds tags to the strings that are tags
//Adds notTags to the searchString with tags filtered out.
//Return number of tokens
int Parser::findTags(vector<string> searchString, vector<string>* tags, vector<string>* notTags){
	if (!tags || !notTags) {
		throw "invalid find tags";
	}
    assert(tags);//Assume tags/notTags are initialized.
    assert(notTags);
    for(int i=0 ; i<searchString.size() ; i++){
        if(searchString[i].at(0)=='#'){
            searchString[i].erase(searchString[i].begin());
            tags->push_back(searchString[i]);
        }else{
            notTags->push_back(searchString[i]);
        }
    }
    return tags->size();
}

//@author A0096566M
//Parses a string to a task
//taskname dates tags
Task Parser::stringToTask(string s){
	log(LOG_DEBUG) << " Parser::stringToTask - Parsing: \"" << s << "\"to task";
    if(s == ""){
		log(LOG_DEBUG) << " Parser::stringToTask - Empty string.";
        return Task(false);
    }
    Task t(true);
    vector<string> tokens = tokenize(s," ");
    //Look for tags
        vector<string> tags;
        vector<string> notTags;
        findTags(tokens,&tags,&notTags);
    //Look for dates
        vector<Date> dates;
        vector<string> notDates;
        findDates(notTags,&dates,&notDates);
    vector<string> temp = tokenize(concatenate(notDates)," ");
    string tName = concatenate(temp);
    //Check for date(s) present.
    if(dates.size()<=2){//Can have 2 dates at max.
        if(tName!=""){
            t.setTaskName(tName);
            t.setCategories(tags);
            if(dates.size()==1){
                t.setDeadline(dates[0]);
            } else if(dates.size() == 2){
                t.setStartEnd(dates[0],dates[1]);
            }
            else {
                t.setFloating();
            }
        } else{
            return Task(false);
        }
    } else {
        //If more than 2 dates, take all as task name.
		log(LOG_DEBUG) << " Parser::stringToTask - More than two dates found, interpreting as task name without any dates.";
        if(tName!=""){
            t.setTaskName(concatenate(notTags));
            t.setCategories(tags);
			t.setFloating();
        } else {
            return Task(false);
        }
    }

	log(LOG_DEBUG) << " Parser::stringToTask - Done parsing.";
    return t;
}

//@author A0096566M
void Parser::setTaskListRecords(Records* rec){
    this->taskList = rec;
}

//@author A0096566M
//Parse userinput and give a list of possible valid commands.
vector<string> Parser::getFeedback(string userinput){
	log(LOG_DEBUG) << "Parser::getFeedback - Getting suggestions for sting: " << userinput;
    //Clear previous commandList.
    commandList.clear();

    vector<string> suggestions;
    vector<string> tokens = tokenize(userinput," ");
    //Look for tags
		log(LOG_DEBUG) << "Parser::getFeedback - Looking for tags";
        vector<string> tags;
        vector<string> notTags;
        findTags(tokens,&tags,&notTags);
    //Look for dates
		log(LOG_DEBUG) << "Parser::getFeedback - Looking for dates";
        vector<Date> dates;
        vector<string> notDates;
        findDates(notTags,&dates,&notDates);

    //1. If no input
    if(tokens.size() == 0){	
		log(LOG_DEBUG) << "Parser::getFeedback - Suggest available commands";
        //Push invalid commands into commandList.
        for(int i=0 ; i<BASIC_COMMANDS.size() ; i++){
            commandList.push_back(new Cmd(INVALID,vector<string>(),BASIC_COMMANDS[i]));
        }
        //return vector of standard commands
        suggestions = BASIC_COMMANDS;
        return suggestions;
    }

    //2. If only one token, try to match basic commands.
    if(tokens.size() == 1){	
		log(LOG_DEBUG) << "Parser::getFeedback - Matching available commands";
        //Try to match basic commands
        vector<string> matchingCommands = filterBeginsWith(BASIC_COMMANDS,tokens[0]);
        for(int i=0 ; i<matchingCommands.size() ; i++){
            //Push matching commands to suggestion list and create corresponding invalid Cmd for it.
            suggestions.push_back(matchingCommands[i]);
            commandList.push_back(new Cmd(INVALID,vector<string>(),BASIC_COMMANDS[i]));
        }
    }

    //3. Check first token if command is explicitly given(default or additional command strings.)
    int commandIndex = isBasicCommand(tokens[0]);
    if(commandIndex != -1){	
		log(LOG_DEBUG) << "Parser::getFeedback - Found command";
        string commandToken = tokens[0];
        tokens.erase(tokens.begin());
        //Deal with cases with command as first token below.
        if(commandToken==Parser::COMMAND_ADD){
			
			log(LOG_DEBUG) << "Parser::getFeedback - Suggestions for ADD";
            //3.1. Add command
            //Give other possible interpretations of add command as well.
            //If date(s) present, take it as task name.
            //If more than 2 dates/time?

            //From notTags, identify dates and take them as name(conbinations of it)
            //Concat and tokenize notTags.
            //Find dates from this.
            //for each combination of dates found, place it as name
            //find dates again, do ^above and push into suggestion list.

            vector<string> catNotTags = tokenize(concatenate(notTags)," ");
            vector<Date>* tempDates = new vector<Date>();
            vector<string>* tempNotDates = new vector<string>();
            findDates(catNotTags,tempDates, tempNotDates);
            int startOfDateTokens = std::find(tempNotDates->begin(), tempNotDates->end(),"") - tempNotDates->begin();
            //if(std::find(tempNotDates->begin(), tempNotDates->end(),"") != tempNotDates->end()){
            if(true){
                for(int i=startOfDateTokens ; i<=catNotTags.size() ; i++){
                    vector<string> temp;
                    temp.insert(temp.begin(),catNotTags.begin(),catNotTags.begin()+i);
                    vector<string> temp2;
                    temp2.insert(temp2.begin(),catNotTags.begin()+i,catNotTags.end());
                    tempDates->clear();
                    tempNotDates->clear();
                    findDates(temp2,tempDates,tempNotDates);
                    temp.insert(temp.end(),tempNotDates->begin(), tempNotDates->end());

                    if(temp.size()>0)
                        temp.erase(temp.begin());
                    string tName = concatenate(temp);
                    tName = concatenate(tokenize(tName, " "));
                    //Check for date(s) present.
                    if(tempDates->size()<=2){//Can have 2 dates at max.
                        if(tName!=""){
                            Task task(true);
                            task.setTaskName(tName);
                            task.setCategories(tags);
                            if(tempDates->size()==1){
                                Date d0 = tempDates->at(0);
                                task.setDeadline(d0);
                            } else if(tempDates->size() == 2){
                                Date d0 = tempDates->at(0);
                                Date d1 = tempDates->at(1);
                                task.setStartEnd(d0,d1);
                            }else {
                                task.setFloating();
                            }
                            if(find(suggestions.begin(),suggestions.end(),(Parser::COMMAND_ADD + " " + task.toHTMLString())) == suggestions.end()){
                                suggestions.push_back(Parser::COMMAND_ADD + " " + task.toHTMLString());
                                commandList.push_back(new CmdAdd(ADD,task,vector<string>(),userinput));
                            }
                        }
                    }

                }
            }
        } else if(commandToken == Parser::COMMAND_UPDATE || commandToken == Parser::COMMAND_DELETE || commandToken == Parser::COMMAND_DONE || commandToken == Parser::COMMAND_UNDONE){         
		log(LOG_DEBUG) << "Parser::getFeedback - Suggestions for update,delete,done/undone";
			//3.2. update,delete,done/undone
            //Autocomplete typing task.
                vector<string> tempSuggestions;
                vector<Cmd*> tempCommandList;
                Records results;
                if(concatenate(tokens)!=""){
                    results = this->taskList->filterByTaskNameContains(true, concatenate(tokens));
                }

                if(commandToken == Parser::COMMAND_DONE){
                    results = results.filterByDone(true, false);
                } else if(commandToken == Parser::COMMAND_UNDONE){
                    results = results.filterByDone(true,  true);
                }

                for(int i=0 ; i<results.getRecordSize() ; i++){
                    suggestions.push_back(commandToken + " " + results.getData(i).getTaskName());
                    commandList.push_back(new Cmd(INVALID,vector<string>(),""));
                }
            //Case where user used a (valid) index:
            if(tokens.size()>0){
            int index=-1;
            try{
                index = stoi(tokens[0]);
            }catch(...){
                index = -1;
            }
            if (index >= 1 && index <= displayRecords->getRecordSize()) {
                string stringIndex = tokens[0];
                //For update only, autocomplete task and give possible combinations.
                if(commandToken == Parser::COMMAND_UPDATE){
                    vector<string> catNotTags = tokenize(concatenate(notTags)," ");
                    vector<Date>* tempDates = new vector<Date>();
                    vector<string>* tempNotDates = new vector<string>();
                    findDates(catNotTags,tempDates, tempNotDates);
                    int startOfDateTokens = std::find(tempNotDates->begin(), tempNotDates->end(),"") - tempNotDates->begin();
                    if(std::find(tempNotDates->begin(), tempNotDates->end(),"") != tempNotDates->end()){
                        for(int i=startOfDateTokens ; i<=catNotTags.size() ; i++){
                            vector<string> temp;
                            temp.insert(temp.begin(),catNotTags.begin(),catNotTags.begin()+i);
                            vector<string> temp2;
                            temp2.insert(temp2.begin(),catNotTags.begin()+i,catNotTags.end());
                            tempDates->clear();
                            tempNotDates->clear();
                            findDates(temp2,tempDates,tempNotDates);
                            temp.insert(temp.end(),tempNotDates->begin(), tempNotDates->end());

                            if(temp.size()>1){
                                temp.erase(temp.begin());
                                temp.erase(temp.begin());
                            }
                            string tName = concatenate(temp);
                            tName = concatenate(tokenize(tName, " "));
                            //Check for date(s) present.
                            if(tempDates->size()<=2){//Can have 2 dates at max.
                                if(tName!=""){
                                    Task task(true);
                                    task.setTaskName(tName);
                                    task.setCategories(tags);
                                    if(tempDates->size()==1){
                                        Date d0 = tempDates->at(0);
                                        task.setDeadline(d0);
                                    } else if(tempDates->size() == 2){
                                        Date d0 = tempDates->at(0);
                                        Date d1 = tempDates->at(1);
                                        task.setStartEnd(d0,d1);
                                    }else {
                                        task.setFloating();
                                    }
                                    if(find(suggestions.begin(),suggestions.end(),(Parser::COMMAND_UPDATE + " " + stringIndex + " "+ task.toHTMLString())) == suggestions.end()){
                                        suggestions.push_back(Parser::COMMAND_UPDATE + " " + stringIndex + " " + task.toHTMLString());
										int id = displayRecords->getData(index-1).getId();
                                        commandList.push_back(new CmdUpdate(cmdTypes::UPDATE,vector<string>(),userinput,id,task));	
                                    }
                                }
                            }

                        }
                    }
                }
            }
            }
            suggestions.insert(suggestions.end(),tempSuggestions.begin(),tempSuggestions.end());
            commandList.insert(commandList.end(),tempCommandList.begin(),tempCommandList.end());
        }

        //Replace commandToken into Tokens
        tokens.insert(tokens.begin(),commandToken);
    }

    //4. No/Assume no command explicitly given
        vector<string> tempSuggestions;
        vector<Cmd*> tempCommandList;
        //4.1. Try to match whole string to taskname	
			log(LOG_DEBUG) << "Parser::getFeedback - Searching string as taskname";
            //Take whole string as taskName
            //Search and autocomplete it
            Records results = this->taskList->filterByTaskNameContains(true,  concatenate(tokens));
            for(int i=0 ; i<results.getRecordSize() ; i++){
                tempSuggestions.push_back(results.getData(i).getTaskName());
                tempCommandList.push_back(new Cmd(INVALID,vector<string>(),""));
            }

        //4.2. Add in possible command keywords
            bool found = false;
            if(tempSuggestions.size() > 0){
                if(tempSuggestions[0] == concatenate(tokens) || tempSuggestions.size() == 1){
                    found = true;
                }
            }

            //4.2.1. If exact match, prepend commands.(update,delete)
            if(found){			
				log(LOG_DEBUG) << "Parser::getFeedback - Prepending possible commands";
                //update,delete,done/undone
                tempSuggestions.push_back(Parser::COMMAND_UPDATE + " " + tempSuggestions[0]);
                tempCommandList.push_back(new Cmd(INVALID,vector<string>(),""));
                tempSuggestions.push_back(Parser::COMMAND_DELETE + " " + tempSuggestions[0]);
                tempCommandList.push_back(new Cmd(INVALID,vector<string>(),""));
                if(!results.getData(0).isDone()){
                    tempSuggestions.push_back(Parser::COMMAND_DONE + " " + tempSuggestions[0]);
                    tempCommandList.push_back(new Cmd(INVALID,vector<string>(),""));
                } else {
                    tempSuggestions.push_back(Parser::COMMAND_UNDONE + " " + tempSuggestions[0]);
                    tempCommandList.push_back(new Cmd(INVALID,vector<string>(),""));
                }
            }

            //4.2.2. add command if no exact match
            if(!found){			
				log(LOG_DEBUG) << "Parser::getFeedback - Suggest adding entire string";
                //add
                Task task;
                task.setTaskName(concatenate(notTags));
                task.setCategories(tags);
                task.setFloating();
                tempSuggestions.push_back(Parser::COMMAND_ADD + " " + task.toHTMLString());
                tempCommandList.push_back(new CmdAdd(ADD,task,vector<string>(),""));
            }


        //Try to match to search parameter(due/important/date)

        //Try to find dates in parts of the input
            //If there are tokens before dates, treat as taskname


        suggestions.insert(suggestions.end(),tempSuggestions.begin(),tempSuggestions.end());
        commandList.insert(commandList.end(),tempCommandList.begin(),tempCommandList.end());

	log(LOG_DEBUG) << "Parser::getFeedback - Done Getting suggestions";
    return suggestions;
}
